---
import classNames from '@sindresorhus/class-names';
import CodeBlock from '../components/CodeBlock.astro';
import getNewestEngine from '../lib/newest-engine';
import getComparisons from '../lib/comparisons';
import isEven from '../utils/is-even';
import titleCase from '../utils/title-case';

const comparisons = await getComparisons();
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <title>You Might Not Need jQuery</title>

    <meta
      name="description"
      content="Examples of how to do common event, element, ajax and utility operations with plain javascript."
    />
    <link rel="icon" href="https://static.hubspot.com/favicon.ico" />
  </head>
  <body>
    <div class="main-wrapper">
      <input id="sidebar-toggle" type="checkbox" />
      <nav id="navbar">
        <label id="sidebar-toggle-label" for="sidebar-toggle">
          <span></span>
        </label>
      </nav>
      <nav id="sidebar">
        <ul class="sidebar-categories">
          {
            Object.entries(comparisons).map(([categoryName, category]) => (
              <li class="sidebar-category">
                <div class="sidebar-category-title">
                  <a href={`#${categoryName}`}>{titleCase(categoryName)}</a>
                </div>
                <ul class="sidebar-comparisons">
                  {Object.keys(category.comparisons).map((comparisonName) => (
                    <li class="sidebar-comparison-title">
                      <a href={`#${comparisonName}`}>
                        {titleCase(comparisonName.replaceAll('_', ' '))}
                      </a>
                    </li>
                  ))}
                </ul>
              </li>
            ))
          }
        </ul>
      </nav>
      <main id="main-content">
        <div id="top">
          <header id="top-header">
            <h1 class="title">You might not need jQuery</h1>
          </header>
          <article class="explanation">
            <p>
              jQuery and its cousins are great, and by all means use them if it
              makes it easier to develop your application.
              <br /><br />
              If you're developing a library on the other hand, please take a moment
              to consider if you actually need jQuery as a dependency. Maybe you
              can include a few lines of utility code, and forgo the requirement.
              If you're only targeting more modern browsers, you might not need anything
              more than what the browser ships with.
              <br /><br />
              At the very least, make sure you know what <a
                href="https://docs.google.com/document/d/1LPaPA30bLUB_publLIMF0RlhdnPx_ePXm7oW02iiT6o"
                >jQuery is doing for you</a
              >, and what it's not. Some developers believe that jQuery is
              protecting us from a great demon of browser incompatibility when,
              in truth, post-IE8, browsers are pretty easy to deal with on their
              own; and after the Internet Explorer era, the browsers do even
              more.
            </p>
          </article>
          <address class="share-buttons">
            <a
              href="https://github.com/HubSpot/YouMightNotNeedjQuery"
              class="share-button-github"
            >
              <span class="share-button-github-message">★ Star on Github</span>
              <span class="github-stars"></span>
            </a>
          </address>
          <nav>
            <input
              id="search"
              type="text"
              role="searchbox"
              placeholder="Search..."
              required
            />
            <div class="input-box">
              <label for="ie-question">Do you need to support IE?</label>
              <input type="checkbox" id="ie-question" name="ie-question" />
            </div>
            <div id="ie-version-box" class="input-box hidden">
              <label for="ie-min-version"
                >What's the oldest version of IE you need to support?</label
              >
              <div class="slider">
                <input
                  type="range"
                  id="ie-min-version"
                  name="ie-min-version"
                  min={8}
                  max={11}
                  value={11}
                />
                <table class="slider-labels" aria-hidden={true}>
                  <tr>
                    {
                      [8, 9, 10, 11].map((version) => (
                        <td class="slider-label ie-slider-label">{version}</td>
                      ))
                    }
                  </tr>


                </table>
              </div>
            </div>
          </nav>
        </div>

        <article id="comparisons">
          <div class="empty-message">
            Your search didn't match any comparisons.
          </div>

          <div class="categories">
            {
              Object.entries(comparisons).map(
                ([categoryName, category], index) => (
                  <section
                    class={classNames('category', {
                      even: isEven(index)
                    })}
                    id={categoryName}
                  >
                    <header class="comparisons-header">
                      <h2 class="category-name">
                        <a href={`#${categoryName}`}>
                          {titleCase(categoryName)}
                        </a>
                      </h2>
                      {category.alternatives && (
                        <div class="alternatives">
                          <h3 class="alternatives-title">Alternatives:</h3>
                          <ul class="alternatives-list">
                            {Object.entries(category.alternatives).map(
                              ([alternativeName, alternativeUrl]) => (
                                <li>
                                  <a
                                    href={alternativeUrl}
                                    target="_blank"
                                    rel="noopener"
                                    class="alternative-link"
                                  >
                                    {alternativeName}
                                  </a>
                                </li>
                              )
                            )}
                          </ul>
                        </div>
                      )}
                    </header>
                    <div class="category-comparisons">
                      {Object.entries(category.comparisons).map(
                        ([comparisonName, comparison]) => {
                          const newestEngine = getNewestEngine(
                            Object.keys(comparison.engines)
                          );

                          return (
                            <div class="comparison" id={comparisonName}>
                              <header class="comparisons-header">
                                <h3 class="comparison-title">
                                  <a href={`#${comparisonName}`}>
                                    {titleCase(
                                      comparisonName.replaceAll('_', ' ')
                                    )}
                                  </a>
                                </h3>
                                {comparison.alternatives && (
                                  <div class="alternatives">
                                    <h4 class="alternatives-title">
                                      Alternatives:
                                    </h4>
                                    <ul class="alternatives-list">
                                      {Object.entries(
                                        comparison.alternatives
                                      ).map(
                                        ([alternativeName, alternativeUrl]) => (
                                          <li>
                                            <a
                                              href={alternativeUrl}
                                              target="_blank"
                                              rel="noopener"
                                              class="alternative-link"
                                            >
                                              {alternativeName}
                                            </a>
                                          </li>
                                        )
                                      )}
                                    </ul>
                                  </div>
                                )}
                              </header>
                              <div class="engines">
                                {Object.entries(comparison.engines).map(
                                  ([engineName, allCode]) => (
                                    <div
                                      class={classNames('engine', {
                                        hidden:
                                          engineName !== newestEngine &&
                                          engineName !== 'jquery'
                                      })}
                                      data-engine={engineName}
                                    >
                                      <h4 class="engine-name">
                                        {`${engineName}${
                                          engineName.startsWith('ie') ? '+' : ''
                                        }`}
                                      </h4>
                                      {allCode.map(({language, code}) => (
                                        <CodeBlock
                                          lang={language}
                                          code={code}
                                        />
                                      ))}
                                    </div>
                                  )
                                )}
                              </div>
                            </div>
                          );
                        }
                      )}
                    </div>
                  </section>
                )
              )
            }
          </div>
        </article>

        <footer>
          <p>
            Made by <span id="credit-a"></span>, <span id="credit-b"></span> and
            some engineer gamers at <a href="https://dev.hubspot.com">HubSpot</a
            >.
          </p>
        </footer>
      </main>
    </div>

    <style is:global lang="scss">
      @use 'modern-normalize';
      @use 'prismjs/themes/prism.css';
    </style>

    <style lang="scss">
      $border-color: #eee;
      $sidebar-width: 300px;
      $sidebar-collapsed-screen-width: 828px;
      $navbar-height: 60px;
      $transition-duration: 0.25s;

      ::placeholder {
        color: rgba(0, 0, 0, 0.5);
      }

      html {
        scroll-behavior: smooth;
        scroll-padding-top: $navbar-height;
      }

      body {
        font-family: system-ui, sans-serif;
        line-height: 1.5;
        display: flex;
        flex-direction: column;
        align-items: center;
      }

      .hidden {
        display: none !important;
      }

      .main-wrapper {
        width: 100%;
      }

      #sidebar-toggle {
        display: none;
      }

      #sidebar-toggle:checked ~ #sidebar {
        left: 0;
      }

      #sidebar-toggle:checked ~ #main-content {
        margin-left: $sidebar-width;
      }

      #sidebar-toggle-label {
        position: fixed;
        z-index: 2;
        top: 16px;
        left: 20px;
        width: 26px;
        height: 26px;
        cursor: pointer;

        span,
        span::before,
        span::after {
          display: block;
          position: absolute;
          width: 100%;
          height: 2px;
          background-color: #333;
          transition-duration: $transition-duration;
          top: 50%;
        }

        span::before {
          content: '';
          top: -8px;
        }

        span::after {
          content: '';
          top: 8px;
        }
      }

      #sidebar-toggle:checked + #navbar {
        #sidebar-toggle-label span {
          transform: rotate(45deg);
          &::before {
            top: 0;
            transform: rotate(0deg);
          }
          &::after {
            top: 0;
            transform: rotate(90deg);
          }
        }
      }

      #navbar {
        position: fixed;
        left: 0;
        right: 0;
        background-color: #fff;
        height: $navbar-height;
        border-bottom: 1px solid $border-color;
        z-index: 1;
        display: block;
      }

      #sidebar {
        flex: 0;
        background-color: #fff;
        z-index: 1;
        width: $sidebar-width;
        position: fixed;
        left: 0;
        top: 0;
        bottom: 0;
        overflow: auto;
        padding: 2em;
        transition-duration: $transition-duration;
        margin-top: $navbar-height;
        border-right: 1px solid $border-color;
        left: -100%;

        @media screen and (max-width: $sidebar-collapsed-screen-width) {
          width: 100%;
        }

        ul {
          list-style: none;
          padding-left: 0;
        }

        a {
          display: block;
          padding: 4px;
          text-decoration: none;
          color: inherit;

          &.active {
            background: rgba(0, 0, 0, 0.1);
          }

          &:hover {
            background: rgba(0, 0, 0, 0.05);
          }
        }

        .sidebar-category {
          margin-bottom: 1em;

          .sidebar-category-title {
            font-size: 20px;
          }

          .sidebar-comparison-title {
            color: #666;
          }

          .sidebar-category-title,
          .sidebar-comparison-title {
            font-weight: 300;
          }
        }
      }

      #main-content {
        align-items: center;
        display: flex;
        flex-direction: column;
        margin-left: 0;
        margin-top: $navbar-height;
        transition-duration: $transition-duration;
      }

      #top {
        display: flex;
        flex-direction: column;
        max-width: 40rem;
        gap: 2rem;
        margin-left: 16px;
        margin-right: 16px;
        margin-top: 4rem;
        margin-bottom: 3rem;

        @media (max-width: 42rem) {
          margin-top: 16px;
        }

        #top-header {
          text-align: center;

          .title {
            padding: 2rem 2rem;
            border: 0.25rem solid;
            font-size: 2.5rem;
            font-weight: 300;
            margin: 0;
            line-height: 1;
          }
        }

        .explanation {
          max-width: 100%;

          a {
            color: inherit;
            text-decoration: none;
            box-shadow: inset 0 -0.125rem;
          }

          p {
            margin: 0;
          }
        }

        .share-buttons {
          display: flex;
          justify-content: center;

          .share-button-github {
            display: flex;
            color: inherit;
            margin-left: auto;
            margin-right: auto;
            text-decoration: none;
            font-style: normal;

            > span {
              border: 1px solid #ccc;
              padding: 0.5rem 0.8rem;

              &.share-button-github-message {
                margin-right: -1px;
                background: #eee;
              }

              &.github-stars {
                text-align: center;
                min-width: 3rem;

                &:empty::after {
                  content: '···';
                }
              }
            }
          }
        }

        nav {
          display: flex;
          flex-direction: column;
          gap: 10px;

          #search {
            max-width: 40rem;

            border: 0;
            font-family: inherit;
            font-weight: 400;
            color: inherit;
            background: rgba(0, 0, 0, 0.1);
            width: 100%;
            padding: 1rem;

            &:focus {
              background: rgba(0, 0, 0, 0.05);
              outline: none;
            }
          }

          @media print {
            display: none;
          }
        }

        .input-box {
          display: flex;
          justify-content: space-between;
          align-items: center;

          input[type='checkbox'] {
            height: 20px;
            width: 20px;
          }

          input {
            accent-color: black;
          }

          .slider {
            .slider-labels {
              border-collapse: collapse;
              min-width: 100%;
              margin-left: 3px;
              margin-right: 3px;

              .slider-label {
                text-align: center;

                &:first-child {
                  text-align: left;
                }

                &:last-child {
                  text-align: right;
                }

                &.ie-slider-label {
                  // 25% for each of the 4 versions
                  width: 25%;

                  &.ie-slider-label:nth-child(2) {
                    padding-right: 12px;
                  }
                }
              }
            }
          }
        }
      }

      #comparisons {
        width: 100%;

        .empty-message {
          text-align: center;
          background: #eee;
          padding-top: 5.5rem;
          padding-bottom: 5.5rem;
          font-weight: 500;
          font-size: 1.2rem;
          display: none;
        }

        &.no-search-results {
          .empty-message {
            display: block;
          }
        }

        .categories {
          .comparisons-header {
            display: flex;
            flex-direction: column;
            gap: 10px;
          }

          .category {
            padding-top: 5.5rem;
            padding-bottom: 5.5rem;
            padding-left: 16px;
            padding-right: 16px;
            display: flex;
            flex-direction: column;
            gap: 5.5rem;

            @mixin section-symbol {
              // :after is used to ensure the text is centered - this is never shown
              &:before,
              &:after {
                content: '§';
                visibility: hidden;
              }

              &:hover:before {
                visibility: visible;
              }
            }

            .category-name {
              font-size: 3rem;
              font-weight: 200;
              letter-spacing: 1rem;
              text-transform: uppercase;
              color: #888;
              text-align: center;
              margin: 0;
              word-wrap: break-word;

              a {
                color: inherit;
                text-decoration: none;

                @include section-symbol;

                // At such a small screen size, we have to remove this text so that the title remains centered
                @media (max-width: 28rem) {
                  &:before,
                  &:after {
                    content: '';
                  }
                }
              }
            }

            .category-comparisons {
              display: flex;
              flex-direction: column;
              gap: 5.5rem;

              .comparison {
                display: flex;
                flex-direction: column;
                gap: 16px;

                .comparison-title {
                  text-align: center;
                  font-size: 2em;
                  font-weight: normal;
                  margin: 0;

                  a {
                    color: inherit;
                    text-decoration: none;

                    @include section-symbol;
                  }
                }
              }
            }

            .alternatives {
              display: flex;
              justify-content: center;
              gap: 10px;
              flex-wrap: wrap;

              .alternatives-title {
                font-size: 1.1rem;
                font-weight: 300;
                color: #666;
                letter-spacing: 0.07em;
                font-size: 1.4em;
                text-transform: uppercase;
                margin: 0;
                line-height: 1;
                word-wrap: break-word;
              }

              .alternatives-list {
                margin: 0;
                font-size: 1.1em;
                list-style: none;
                padding: 0;
                display: flex;
                gap: 10px;
                flex-wrap: wrap;
                justify-content: center;

                .alternative-link {
                  color: inherit;
                  text-decoration-thickness: 3px;
                  text-decoration-line: underline;
                  text-decoration-skip-ink: none;
                }
              }
            }

            &.even {
              background: #eee;

              :global(.code) {
                background: white;
              }
            }

            .engines {
              display: flex;
              flex-direction: row;
              flex-wrap: wrap;
              justify-content: center;
              gap: 24px;
              margin: 0 auto;
              width: 100%;

              .engine {
                width: 30rem;
                min-width: 0;

                .engine-name {
                  font-weight: 300;
                  color: #666;
                  letter-spacing: 0.07em;
                  font-size: 1.4em;
                  text-transform: uppercase;
                  margin-bottom: 0;
                  margin-top: 8px;
                  line-height: 1;
                }
              }
            }
          }
        }
      }

      footer {
        margin: 3em auto;
        font-size: 1.4em;
        text-align: center;
        padding-left: 16px;
        padding-right: 16px;

        a {
          color: inherit;
        }
      }
    </style>

    <script>
      import getNewestEngine from '../lib/newest-engine';
      import isEven from '../utils/is-even';
      import throttle from '../utils/throttle';

      const adamfschwartz = document.createElement('a');
      adamfschwartz.href = 'https://twitter.com/adamfschwartz';
      adamfschwartz.textContent = '@adamfschwartz';
      adamfschwartz.style.color = 'inherit';

      const zackbloom = document.createElement('a');
      zackbloom.href = 'https://twitter.com/zackbloom';
      zackbloom.textContent = '@zackbloom';
      zackbloom.style.color = 'inherit';

      const creditA = document.querySelector('#credit-a');
      const creditB = document.querySelector('#credit-b');

      if (Math.random() >= 0.5) {
        creditA.replaceWith(adamfschwartz);
        creditB.replaceWith(zackbloom);
      } else {
        creditA.replaceWith(zackbloom);
        creditB.replaceWith(adamfschwartz);
      }

      const searchInput: HTMLInputElement = document.querySelector('#search');
      const comparisonsParent = document.querySelector('#comparisons');
      const versionBox = document.querySelector('#ie-version-box');
      const ieQuestion =
        document.querySelector<HTMLInputElement>('#ie-question');
      const ieMinVersion =
        document.querySelector<HTMLInputElement>('#ie-min-version');

      document.addEventListener('keydown', (event) => {
        if (event.key === '/' && searchInput !== document.activeElement) {
          event.preventDefault(); // Firefox uses this for quick find
          searchInput.focus();
        }
      });

      function resetResults() {
        comparisonsParent.classList.remove('no-search-results');

        for (const hiddenCategoryOrComparison of document.querySelectorAll(
          '.category.hidden, .comparison.hidden'
        )) {
          hiddenCategoryOrComparison.classList.remove('hidden');
        }

        for (const [index, category] of document
          .querySelectorAll('.category')
          .entries()) {
          category.classList.toggle('even', isEven(index));
        }
      }

      searchInput.addEventListener('input', (event) => {
        const query = (event.target as HTMLInputElement).value.toLowerCase();

        resetResults();

        if (query.length === 0) {
          return;
        }

        const categories = document.querySelectorAll('.category');
        let hiddenCategories = 0;

        for (const [index, category] of categories.entries()) {
          const comparisons = category.querySelectorAll('.comparison');
          let hiddenComparisons = 0;

          for (const comparison of comparisons) {
            if (!comparison.textContent.toLowerCase().includes(query)) {
              comparison.classList.add('hidden');
              hiddenComparisons++;
            }
          }

          if (hiddenComparisons === comparisons.length) {
            category.classList.add('hidden');
            hiddenCategories++;
          }
          {
            category.classList.toggle('even', isEven(index - hiddenCategories));
          }
        }

        if (hiddenCategories === categories.length) {
          comparisonsParent.classList.add('no-search-results');
        }
      });

      function setQueryString(supportedVersion?: string) {
        if (supportedVersion) {
          const urlParams = new URLSearchParams(location.search);
          urlParams.set('support', supportedVersion);
          history.replaceState({}, '', '?' + urlParams);
        } else {
          history.replaceState({}, '', location.pathname);
        }
      }

      function updateEngines(targetVersion?: string) {
        setQueryString(targetVersion);

        for (const engine of document.querySelectorAll(
          '[data-engine].hidden'
        )) {
          engine.classList.remove('hidden');
        }

        for (const engines of document.querySelectorAll('.engines')) {
          const engineElements = [
            ...engines.querySelectorAll<HTMLDivElement>(
              `[data-engine=ie8], [data-engine=ie9], [data-engine=ie10], [data-engine=ie11], [data-engine=modern]`
            )
          ];

          const bestEngine = getNewestEngine(
            engineElements.map((engine) => engine.dataset.engine),
            targetVersion
          );

          for (const engine of engines.querySelectorAll(
            `.engine:not([data-engine=${bestEngine}]):not([data-engine=jquery])`
          )) {
            engine.classList.add('hidden');
          }
        }
      }

      function getInitialSliderState(): string | undefined {
        const urlParams = new URLSearchParams(location.search);
        return urlParams.get('support');
      }
      const initialSliderState = getInitialSliderState();
      if (initialSliderState) {
        if (initialSliderState.includes('ie')) {
          ieQuestion.checked = true;
          ieMinVersion.value = initialSliderState.replace('ie', '');
          versionBox.classList.remove('hidden');
        }
        updateEngines(initialSliderState);
      }

      ieQuestion.addEventListener('change', () => {
        const isChecked = ieQuestion.checked;

        versionBox.classList.toggle('hidden', !isChecked);

        if (isChecked) {
          updateEngines(`ie${ieMinVersion.value}`);
        } else {
          updateEngines();
        }
      });

      ieMinVersion.addEventListener('change', () => {
        updateEngines(`ie${ieMinVersion.value}`);
      });

      function findActiveCategoryOrComparison(): string | undefined {
        let closestElementY = 0;
        let anchor: string;
        // First, check category names
        for (let el of document.querySelectorAll('.category-name')) {
          const elementY = (el as HTMLElement).offsetTop;
          // Element is hidden
          if (elementY === 0) {
            continue;
          }
          if (elementY >= window.scrollY) {
            closestElementY = elementY;
            anchor = el.querySelector('a').href.split('#').pop();
            break;
          }
        }
        for (let el of document.querySelectorAll('.comparison-title')) {
          const elementY = (el as HTMLElement).offsetTop;
          // Don't evaluate nodes beyond the closest category
          if (elementY > closestElementY) {
            break;
          }
          // Element is hidden
          if (elementY === 0) {
            continue;
          }
          const diff = elementY - window.scrollY;
          const isCloser =
            diff >= 0 &&
            (!closestElementY || diff < closestElementY - window.scrollY);
          if (isCloser) {
            anchor = el.querySelector('a').href.split('#').pop();
            break;
          }
        }
        return anchor;
      }
      function markSidebarAnchorActive(anchorName?: string) {
        // Clear out active anchors first
        const sidebar: HTMLDivElement = document.querySelector('#sidebar');
        sidebar.querySelectorAll('a').forEach((el) => {
          if (!anchorName || el.href !== `#${anchorName}`)
            el.classList.remove('active');
        });
        if (anchorName) {
          const maybeAnchor: HTMLAnchorElement | null = sidebar.querySelector(
            `a[href='#${anchorName}']`
          );
          if (maybeAnchor) {
            maybeAnchor.classList.add('active');
          }
        }
      }
      window.addEventListener(
        'scroll',
        throttle(() => {
          const activeCategoryOrComparison = findActiveCategoryOrComparison();
          markSidebarAnchorActive(activeCategoryOrComparison);
        }, 100)
      );
      const activeCategoryOrComparison = findActiveCategoryOrComparison();
      markSidebarAnchorActive(activeCategoryOrComparison);

      function readLocalStorage<T>(key: string, defaultValue: T): T {
        try {
          const rawItem = window.localStorage.getItem(key);
          if (rawItem == null) return defaultValue;
          return JSON.parse(rawItem);
        } catch (err) {
          return defaultValue;
        }
      }
      function writeLocalStorage<T>(key: string, value: T) {
        try {
          window.localStorage.setItem(key, JSON.stringify(value));
        } catch (err) {
          // Swallow error
        }
      }

      const MOBILE_WIDTH = 828;
      const sidebar: HTMLDivElement = document.querySelector('#sidebar');
      const sidebarToggle: HTMLInputElement =
        document.querySelector('#sidebar-toggle');
      const SIDEBAR_LOCAL_STORAGE_KEY = 'sidebar-open';
      const sidebarDefaultChecked = readLocalStorage(
        SIDEBAR_LOCAL_STORAGE_KEY,
        // Default the sidebar toggle open when on larger screens
        window.innerWidth >= MOBILE_WIDTH
      );
      sidebarToggle.checked = sidebarDefaultChecked;
      sidebarToggle.addEventListener('change', () => {
        const isChecked = sidebarToggle.checked;
        writeLocalStorage(SIDEBAR_LOCAL_STORAGE_KEY, isChecked);
      });
      sidebar.querySelectorAll('a').forEach((el) => {
        el.addEventListener('click', () => {
          const sidebarToggle: HTMLInputElement =
            document.querySelector('#sidebar-toggle');
          // Only close the sidebar automatically on mobile
          if (window.innerWidth < MOBILE_WIDTH) {
            sidebarToggle.checked = false;
          }
        });
      });

      const numberFormat = new Intl.NumberFormat('en-US');
      const starsText = document.querySelector('.github-stars');

      async function getStars(): Promise<number> {
        const response = await fetch(
          'https://api.github.com/repos/HubSpot/youmightnotneedjquery'
        );

        if (!response.ok) {
          throw new Error('Failed to fetch GitHub stars');
        }

        const {stargazers_count: stars} = await response.json();

        return stars;
      }

      try {
        starsText.textContent = numberFormat.format(await getStars());
      } catch {
        starsText.textContent = '10k+';
      }
    </script>
  </body>
</html>
